﻿#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#ifdef _WIN32
#include <Windows.h>
#endif
#include "n2base.h"

#define OUTPUT_STR_BUFF_SIZE    (64*1024)

int (*n2print)(const char *format, ...) = NULL;
int (*n2prterr)(const char *format, ...) = NULL;
int (*n2vprint)(const char *format, va_list vargs) = NULL;
int (*n2vprterr)(const char *format, va_list vargs) = NULL;

static int stdoutconsole = 0, stderrconsole = 0;
static int stderr_exist = 1;
static HANDLE hStdOut, hStdErr;
static int nls_id;
static char outbuff[OUTPUT_STR_BUFF_SIZE+4];

static int PrintfUtf8_Windows_Console(const char *format, ...);
static int PrintfUtf8_Windows_ToFile(const char *format, ...);
static int VPrintfUtf8_Windows_Console(const char *format, va_list vargs);
static int VPrintfUtf8_Windows_ToFile(const char *format, va_list vargs);
static int PrintfUtf8_Windows_Null(const char *format, ...);

static int PerrorUtf8_Windows_Console(const char *format, ...);
static int VPerrorUtf8_Windows_Console(const char *format,  va_list vargs);
static int PerrorUtf8_Windows_ToFile(const char *format, ...);
static int VPerrorUtf8_Windows_ToFile(const char *format, va_list vargs);
static int PerrorUtf8_Windows_Null(const char *format, ...);
static int VPerrorUtf8_Windows_Null(const char *format, va_list vargs);


static void win_locale_init(void);

int n2base_init()
{
    DWORD  mode;
    BOOL   succeed;

    if (is_little_endian())
        little_endian = 1;
    else
        little_endian = 0;

    hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    hStdErr = GetStdHandle(STD_ERROR_HANDLE);
    if ( hStdOut == INVALID_HANDLE_VALUE || hStdOut == NULL )
        return -1;
    if ( hStdErr == INVALID_HANDLE_VALUE || hStdErr == NULL )
        stderr_exist = 0;

    succeed = GetConsoleMode(hStdOut, &mode);
    if (succeed)                                            // the stdout is console
        stdoutconsole = 1;
    else {                                                  // the stdout is file
		DWORD fileSizeLo;
		DWORD fileSizeHi;
		DWORD error;
		fileSizeLo = GetFileSize(hStdOut, &fileSizeHi);
		error = GetLastError();
		if (fileSizeLo == INVALID_FILE_SIZE && error != NO_ERROR)
			;
		else
        {
            n2u64 u64Size;
            u64Size = (n2u64)fileSizeHi;
            u64Size <<= 32;
            u64Size |= fileSizeLo;
            if (u64Size == 0) {                             // add UTF-8 BOM
                fputc(0xef, stdout);
                fputc(0xbb, stdout);
                fputc(0xbf, stdout);
            }
        }
    }

    if (stderr_exist) {
        succeed = GetConsoleMode(hStdErr, &mode);
        if (succeed)
            stderrconsole = 1;
        else {
            DWORD fileSizeLo;
		    DWORD fileSizeHi;
		    DWORD error;
		    fileSizeLo = GetFileSize(hStdOut, &fileSizeHi);
		    error = GetLastError();
		    if (fileSizeLo == INVALID_FILE_SIZE && error != NO_ERROR)
			    ;
            else {
                n2u64 u64Size;
                u64Size = (n2u64)fileSizeHi;
                u64Size <<= 32;
                u64Size |= fileSizeLo;
                if (u64Size == 0) {                             // add UTF-8 BOM
                    fputc(0xef, stderr);
                    fputc(0xbb, stderr);
                    fputc(0xbf, stderr);
                }
            }
        }
    }

    if (stderr_exist) {
        if (stderrconsole) {
            n2prterr = PerrorUtf8_Windows_Console;
            n2vprterr = VPerrorUtf8_Windows_Console;
        }
        else {
            n2prterr = PerrorUtf8_Windows_ToFile;
            n2vprterr = VPerrorUtf8_Windows_ToFile;
        }
    } 
    else {
        n2prterr = PerrorUtf8_Windows_Null;
        n2vprterr = VPerrorUtf8_Windows_Null;
    }

    if (stdoutconsole) {
        n2print = PrintfUtf8_Windows_Console;
        n2vprint = VPrintfUtf8_Windows_Console;
    }
    else {
        n2print = PrintfUtf8_Windows_ToFile;
        n2vprint = VPrintfUtf8_Windows_ToFile;
    }

    // Init locale
    win_locale_init();

    return 0;
}

static int WinVOutput_Utf8_String_Console(HANDLE handle, const char *format, va_list vargs)
{
    int r;
    size_t len, n, wcount;
    const unsigned char * pb;
    static wchar_t wbuff[128+2];
    if (format == NULL || format[0] == 0)
        return 0;
#ifdef _MSC_VER
#if _MSC_VER >= 1400
    r = vsnprintf(outbuff, OUTPUT_STR_BUFF_SIZE, format, vargs);
#else
    r = _vsnprintf(outbuff, OUTPUT_STR_BUFF_SIZE, format, vargs);
#endif
#else
    r = vsnprintf(outbuff, OUTPUT_STR_BUFF_SIZE, format, vargs);
#endif
    outbuff[OUTPUT_STR_BUFF_SIZE] = 0;
    len = strlen(outbuff);
    n = wcount = 0;
    pb = (unsigned char*) outbuff;
    while ( n < len )
    {
        if (isUTF8Style1(pb[n])) {
            wbuff[wcount++] = (wchar_t) UTF8ToU32CharS1(pb[n]);
            n ++;
        } else if (isUTF8Style2(pb[n], pb[n+1])) {
            wbuff[wcount++] = (wchar_t) UTF8ToU32CharS2(pb[n], pb[n+1]);
            n += 2;
        } else if (isUTF8Style3(pb[n], pb[n+1], pb[n+2])) {
            wbuff[wcount++] = (wchar_t) UTF8ToU32CharS3(pb[n], pb[n+1], pb[n+2]);
            n += 3;
        } else if (isUTF8Style4(pb[n], pb[n+1], pb[n+2], pb[n+3])) {
            wbuff[wcount++] = L'?';
            n += 4;
        } else if (isUTF8Style5(pb[n], pb[n+1], pb[n+2], pb[n+3], pb[n+4])) {
            wbuff[wcount++] = L'?';
            n += 5;
        } else if (isUTF8Style6(pb[n], pb[n+1], pb[n+2], pb[n+3], pb[n+4], pb[n+5])) {
            wbuff[wcount++] = L'?';
            n += 6;
        } else {
            wbuff[wcount++] = (wchar_t) pb[n];
            n ++;
        }
        //
        if (wcount >= 128) {
            DWORD NumberOfCharsWritten;
            wbuff[wcount] = 0;
            WriteConsoleW(handle, wbuff, (DWORD) wcslen(wbuff), &NumberOfCharsWritten, NULL);
            wcount = 0;
        }
    }

    if (wcount) {
        DWORD NumberOfCharsWritten;
        wbuff[wcount] = 0;
        WriteConsoleW(handle, wbuff, (DWORD) wcslen(wbuff), &NumberOfCharsWritten, NULL);
        wcount = 0;
    }
    
    return r;
}

static int WinVOutput_Utf8_String_File(FILE* fp, const char *format, va_list vargs)
{
    int r;
    if (format == NULL || format[0] == 0)
        return 0;
#ifdef _MSC_VER
#if _MSC_VER >= 1400
    r = vsnprintf(outbuff, OUTPUT_STR_BUFF_SIZE, format, vargs);
#else
    r = _vsnprintf(outbuff, OUTPUT_STR_BUFF_SIZE, format, vargs);
#endif
#else
    r = vsnprintf(outbuff, OUTPUT_STR_BUFF_SIZE, format, vargs);
#endif
    outbuff[OUTPUT_STR_BUFF_SIZE] = 0;
    fwrite(outbuff, 1, strlen(outbuff), fp);
    return r;
}

static int PrintfUtf8_Windows_Console(const char *format, ...)
{
    int r;
    va_list vargs;
    va_start(vargs, format);
    r = WinVOutput_Utf8_String_Console(hStdOut, format, vargs);
    va_end(vargs);
    return r;
}

static int VPrintfUtf8_Windows_Console(const char *format, va_list vargs)
{
    return WinVOutput_Utf8_String_Console(hStdOut, format, vargs);
}

static int PrintfUtf8_Windows_ToFile(const char *format, ...)
{
    int r;
    va_list vargs;
    va_start(vargs, format);
    r = WinVOutput_Utf8_String_File(stdout, format, vargs);
    va_end(vargs);
    return r;
}

static int VPrintfUtf8_Windows_ToFile(const char *format, va_list vargs)
{
    return WinVOutput_Utf8_String_File(stdout, format, vargs);
}

static int PrintfUtf8_Windows_Null(const char *format, ...)
{
    return 0;
}

static int PerrorUtf8_Windows_Console(const char *format, ...)
{
    int r;
    va_list vargs;
    va_start(vargs, format);
    r = WinVOutput_Utf8_String_Console(hStdErr, format, vargs);
    va_end(vargs);
    return r;
}

static int VPerrorUtf8_Windows_Console(const char *format,  va_list vargs)
{
    return WinVOutput_Utf8_String_Console(hStdErr, format, vargs);
}

static int PerrorUtf8_Windows_ToFile(const char *format, ...)
{
    int r;
    va_list vargs;
    va_start(vargs, format);
    r = WinVOutput_Utf8_String_File(stderr, format, vargs);
    va_end(vargs);
    return r;
}

static int VPerrorUtf8_Windows_ToFile(const char *format, va_list vargs)
{
    return WinVOutput_Utf8_String_File(stderr, format, vargs);
}

static int PerrorUtf8_Windows_Null(const char *format, ...)
{
    return 0;
}

static int VPerrorUtf8_Windows_Null(const char *format, va_list vargs)
{
    return 0;
}

static void win_locale_init(void)
{
    LANGID langid;
    nls_id = 0;
    langid = GetSystemDefaultLangID();
    switch (langid)
    {
    case 0x0404:
        nls_id = N2_LOCALE_zh_TW;
        break;
    case 0x0804:
        nls_id = N2_LOCALE_zh_CN;
        break;
    case 0x0409:
        nls_id = N2_LOCALE_en_US;
        break;
    default:
        nls_id = N2_LOCALE_en_US;
        break;
    }
    return;
}

int n2base_get_nlsid()
{
    return nls_id;
}
